Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

core/database/src/commonTest/kotlin/org/meshtastic/core/database/DbFlowRecoveryTest.kt 41414f832f938b655c97bc07509bc140ea16e2a3 (41414f83) Text, 4.54 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.database

Tff7b72import T7ee787app.cash.turbine.test
Tff7b72import T7ee787kotlinx.coroutines.flow.flow
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds

T8b949e/**
* Verifies the restart contract that keeps process-lifetime eager StateFlows alive across a Room pool wedge (#6608): a
* terminal acquire-timeout must restart the upstream, and an unrelated failure must still terminate it.
*/
Tff7b72class T56d364DbFlowRecoveryTest Tb4b4b4{

Tff7b72private Tff7b72fun Td2a8ffpoolTimeoutTb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3IllegalStateExceptionTb4b4b4(Ta5d6ff"Ta5d6ffTimed out attempting to acquire a reader connection from the database poolTa5d6ff"Tb4b4b4)

Tf0883e@Test
Tff7b72fun Td2a8ffpoolTimeoutRestartsTheUpstreamAndEmitsAfterRecoveryTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attempts Tff7b72= T79c0ff0
Te6edf3flow Tb4b4b4{
Te6edf3attempts Tff7b72+Tff7b72= T79c0ff1
Tff7b72if Tb4b4b4(Te6edf3attempts Tff7b72< T79c0ff3Tb4b4b4) Tff7b72throw Te6edf3poolTimeoutTb4b4b4(Tb4b4b4)
Te6edf3emitTb4b4b4(Ta5d6ff"Ta5d6ffnodesTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3retryOnDbPoolFailureTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Tb4b4b4.Te6edf3testTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3EMISSION_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffnodesTa5d6ff"Tb4b4b4, Te6edf3awaitItemTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3cancelAndIgnoreRemainingEventsTb4b4b4(Tb4b4b4)
Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3attemptsTb4b4b4, Ta5d6ff"Ta5d6ffeach recoverable pool failure must restart the upstreamTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e/**
* A closed pool must NOT be retried. `observeCurrentDb` re-latches on the replacement by itself, so retrying adds
* nothing — and it would keep a flow alive that used to terminate, leaving a backoff loop running inside a scope
* that has already gone away.
*/
Tf0883e@Test
Tff7b72fun Td2a8ffclosedPoolFailureIsNotRetriedTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attempts Tff7b72= T79c0ff0
Te6edf3flowTff7b72<Tffa657IntTff7b72> Tb4b4b4{
Te6edf3attempts Tff7b72+Tff7b72= T79c0ff1
Tff7b72throw Te6edf3IllegalStateExceptionTb4b4b4(Ta5d6ff"Ta5d6ffConnection pool is closedTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3retryOnDbPoolFailureTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Tb4b4b4.Te6edf3testTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3EMISSION_TIMEOUTTb4b4b4) Tb4b4b4{ Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffConnection pool is closedTa5d6ff"Tb4b4b4, Te6edf3awaitErrorTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3messageTb4b4b4) Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3attemptsTb4b4b4, Ta5d6ff"Ta5d6ffa closed pool is handled by re-latching, not by retryingTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffstalledFlowIsRestartedTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attempts Tff7b72= T79c0ff0
Te6edf3flow Tb4b4b4{
Te6edf3attempts Tff7b72+Tff7b72= T79c0ff1
Tff7b72if Tb4b4b4(Te6edf3attempts Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Tff7b72throw Te6edf3DbFlowStalledExceptionTb4b4b4(Ta5d6ff"Ta5d6ffno first emissionTa5d6ff"Tb4b4b4)
Te6edf3emitTb4b4b4(T79c0ff1Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3retryOnDbPoolFailureTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Tb4b4b4.Te6edf3testTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3EMISSION_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3awaitItemTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3cancelAndIgnoreRemainingEventsTb4b4b4(Tb4b4b4)
Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3attemptsTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffunrelatedFailuresPropagateTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attempts Tff7b72= T79c0ff0
Te6edf3flowTff7b72<Tffa657IntTff7b72> Tb4b4b4{
Te6edf3attempts Tff7b72+Tff7b72= T79c0ff1
Tff7b72throw Te6edf3IllegalStateExceptionTb4b4b4(Ta5d6ff"Ta5d6ffno such column: bogusTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3retryOnDbPoolFailureTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Tb4b4b4.Te6edf3testTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3EMISSION_TIMEOUTTb4b4b4) Tb4b4b4{ Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffno such column: bogusTa5d6ff"Tb4b4b4, Te6edf3awaitErrorTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3messageTb4b4b4) Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3attemptsTb4b4b4, Ta5d6ff"Ta5d6ffa non-pool failure must not be retriedTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffbackoffResetsAfterASuccessfulEmissionTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72var Te6edf3attempts Tff7b72= T79c0ff0
Te6edf3flow Tb4b4b4{
Te6edf3attempts Tff7b72+Tff7b72= T79c0ff1
Te6edf3emitTb4b4b4(Te6edf3attemptsTb4b4b4)
Tff7b72throw Te6edf3poolTimeoutTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3retryOnDbPoolFailureTb4b4b4(Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Tb4b4b4.Te6edf3testTb4b4b4(Te6edf3timeout Tff7b72= Te6edf3EMISSION_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4, T79c0ff3Tb4b4b4, T79c0ff4Tb4b4b4)Tb4b4b4, Te6edf3ListTb4b4b4(T79c0ff4Tb4b4b4) Tb4b4b4{ Te6edf3awaitItemTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4)
Te6edf3cancelAndIgnoreRemainingEventsTb4b4b4(Tb4b4b4)
Tb4b4b4}

T8b949e// With the backoff reset, four emissions cost four one-second delays rather than exponential growth.
Te6edf3assertTrueTb4b4b4(Te6edf3testSchedulerTb4b4b4.Te6edf3currentTime Tff7b72<Tff7b72= T79c0ff4Te6edf3_000LTb4b4b4, Ta5d6ff"Ta5d6ffsuccessful emissions must reset the retry backoffTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72companion Tff7b72object Tb4b4b4{
T8b949e// Turbine awaits run on the test scheduler's virtual clock, which the retry backoff also advances; this bound
T8b949e// must therefore exceed the capped 30s backoff rather than any wall-clock expectation.
Tff7b72val Te6edf3EMISSION_TIMEOUT Tff7b72= T79c0ff1T79c0ff2T79c0ff0.Te6edf3seconds
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.06s